package me.eg.night;

import javafx.util.Pair;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 1091. 二进制矩阵中的最短路径
 *
 * 给你一个 n x n 的二进制矩阵 grid 中，返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径，返回 -1 。
 *
 * 二进制矩阵中的 畅通路径 是一条从 左上角 单元格（即，(0, 0)）到 右下角 单元格（即，(n - 1, n - 1)）的路径，该路径同时满足下述要求：
 *
 * 路径途经的所有单元格都的值都是 0 。
 * 路径中所有相邻的单元格应当在 8 个方向之一 上连通（即，相邻两单元之间彼此不同且共享一条边或者一个角）。
 * 畅通路径的长度 是该路径途经的单元格总数。
 *
 * 链接：https://leetcode-cn.com/problems/shortest-path-in-binary-matrix
 */
public class ShortestPathBinaryMatrix {
    /**
     * 解法：DFS
     * leetcode 超时
     *
     * @param grid
     * @return
     */
    public int shortestPathBinaryMatrix(int[][] grid) {
        this.grid = grid;
        this.n = grid.length;
        this.shortestPath = n * n;
        this.visited = new boolean[n][n];
        this.direction = new int[][]{{-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}};

        if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return -1;
        if (n == 1 && grid[0][0] == 0) return 1;
        dfs(0, 0, 1);
        return shortestPath == n * n ? -1 : shortestPath;
    }

    private void dfs(int x, int y, int path) {
        if (x == n - 1 && y == n - 1) {
            if (path < shortestPath) shortestPath = path;
            return;
        }

        for (int i = 0; i < 8; i++) {
            int nx = x + direction[i][0];
            int ny = y + direction[i][1];
            if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
            if (grid[nx][ny] == 1) continue;
            if (visited[nx][ny]) continue;
            visited[nx][ny] = true;
            dfs(nx, ny, path + 1);
            visited[nx][ny] = false;
        }
    }

    private int[][] grid;
    private int n;
    private int shortestPath;
    private boolean[][] visited;
    private int[][] direction;

    /**
     * 解法：BFS
     *
     * @param grid
     * @return
     */
    public int shortestPathBinaryMatrix_1(int[][] grid) {
        int n = grid.length;
        if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return -1;
        if (n == 1 && grid[0][0] == 0) return 1;

        boolean[][] visited = new boolean[n][n];
        int[][] direction = new int[][]{{1, 1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 0}, {1, -1}, {0, -1}};
        int depth = 0;
        Queue<Pair<Integer, Integer>> queue = new LinkedList<>();
        queue.offer(new Pair<>(0, 0));
        visited[0][0] = true;
        while (!queue.isEmpty()) {
            int size = queue.size();
            depth++;
            for (int k = 0; k < size; k++) {
                Pair<Integer, Integer> pos = queue.poll();
                int x = pos.getKey();
                int y = pos.getValue();

                for (int i = 0; i < 8; i++) {
                    int nx = x + direction[i][0];
                    int ny = y + direction[i][1];
                    if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
                    if (grid[nx][ny] == 1) continue;
                    if (visited[nx][ny]) continue;
                    if (nx == n - 1 && ny == n - 1) return depth + 1;
                    queue.offer(new Pair<>(nx, ny));
                    visited[nx][ny] = true;
                }
            }
        }
        return -1;
    }

    /**
     * 解法：双向BFS
     *
     * @param grid
     * @return
     */
    public int shortestPathBinaryMatrix_2(int[][] grid) {
        int n = grid.length;
        if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return -1;
        if (n == 1 && grid[0][0] == 0) return 1;

        int[][] direction = new int[][]{{1, 1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 0}, {1, -1}, {0, -1}};

        int[][] startDepth = new int[n][n];
        Queue<Pair<Integer, Integer>> startQ = new LinkedList<>();
        startQ.offer(new Pair<>(0, 0));
        startDepth[0][0] = 1;
        int[][] endDepth = new int[n][n];
        Queue<Pair<Integer, Integer>> endQ = new LinkedList<>();
        endQ.offer(new Pair<>(n - 1, n - 1));
        endDepth[n - 1][n - 1] = 1;

        int curDepth = -1;
        while (!startQ.isEmpty() && !endQ.isEmpty()) {
            curDepth = bfs(grid, startQ, startDepth, endDepth, n, direction);
            if (curDepth > 0) return curDepth;
            curDepth = bfs(grid, endQ, endDepth, startDepth, n, direction);
            if (curDepth > 0) return curDepth;
        }
        return curDepth;
    }

    private int bfs(int[][] grid, Queue<Pair<Integer, Integer>> queue, int[][] depth, int[][] otherDepth, int n, int[][] direction) {
        int size = queue.size();
        for (int k = 0; k < size; k++) {
            Pair<Integer, Integer> pos = queue.poll();
            int x = pos.getKey();
            int y = pos.getValue();
            int curDepth = depth[x][y];

            for (int i = 0; i < 8; i++) {
                int nx = x + direction[i][0];
                int ny = y + direction[i][1];
                if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
                if (grid[nx][ny] == 1) continue;
                if (depth[nx][ny] > 0) continue;
                if (otherDepth[nx][ny] > 0) return otherDepth[nx][ny] + curDepth;
                queue.offer(new Pair<>(nx, ny));
                depth[nx][ny] = curDepth + 1;
            }
        }
        return -1;
    }

}
